This repository has no description
1import { error, redirect } from "@sveltejs/kit";
2import { createBobbinClient } from "$lib/api/client";
3import type { MiniDoc } from "$lib/api/identity";
4import { getProfile, type ProfileRecord } from "$lib/api/records";
5import {
6 enrich,
7 countOf,
8 viewerUriOf,
9 TYPE_COUNT,
10 TYPE_VIEWER,
11 type LinkDescriptor,
12 type LinkSource
13} from "$lib/api/enrich";
14import { toHttpError, httpStatusFor } from "$lib/api/load";
15import { ClientResponseError } from "$lib/api/client";
16import { rkeyFromUri } from "$lib/api/uri";
17import type { ProfileCounts } from "$lib/components/profile/types";
18import type { LayoutLoad } from "./$types";
19
20const REPOS: LinkSource = "sh.tangled.repo:subject";
21const STRINGS: LinkSource = "sh.tangled.string:subject";
22const STARS: LinkSource = "sh.tangled.feed.star:.repo";
23const FOLLOWERS: LinkSource = "sh.tangled.graph.follow:subject";
24const FOLLOWING: LinkSource = "sh.tangled.graph.follow:.repo";
25const VOUCHES: LinkSource = "sh.tangled.graph.vouch:subject";
26
27const COUNT_DESCRIPTORS: LinkDescriptor[] = [
28 REPOS,
29 STRINGS,
30 STARS,
31 FOLLOWERS,
32 FOLLOWING,
33 VOUCHES
34].map((source) => ({ source, type: TYPE_COUNT }));
35
36export const load: LayoutLoad = async (event) => {
37 const parent = await event.parent();
38 const identifier = decodeURIComponent(event.params.handle);
39
40 // rejects bare words so unrelated paths 404 instead of resolving as actors
41 if (!identifier.startsWith("did:") && !identifier.includes(".")) {
42 error(404, "Not found");
43 }
44
45 const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch });
46 const viewerDid = parent.auth?.did;
47 const resolved = await enrich<MiniDoc>(ctx, {
48 xrpc: "com.bad-example.identity.resolveMiniDoc",
49 params: { identifier },
50 enrich: [
51 ...COUNT_DESCRIPTORS,
52 ...(viewerDid ? [{ source: FOLLOWERS, type: TYPE_VIEWER }] : [])
53 ],
54 ...(viewerDid ? { viewer: viewerDid } : {})
55 }).catch((cause) => toHttpError(cause, "Could not resolve user"));
56 const doc = resolved.output;
57
58 // redirects dids and stale handles to the canonical handle url
59 const canonical = doc.handle && !doc.handle.endsWith(".invalid") ? doc.handle : null;
60 if (canonical && identifier.toLowerCase() !== canonical.toLowerCase()) {
61 redirect(307, `/${canonical}${event.url.search}`);
62 }
63
64 const did = doc.did;
65
66 const profile: ProfileRecord | null = await getProfile(ctx, did)
67 .then((view) => view.value)
68 .catch((cause) => {
69 if (cause instanceof ClientResponseError && httpStatusFor(cause) === 404) return null;
70 return toHttpError(cause, "Could not load profile");
71 });
72
73 const counts: ProfileCounts = {
74 repos: countOf(resolved.data, did, REPOS),
75 strings: countOf(resolved.data, did, STRINGS),
76 stars: countOf(resolved.data, did, STARS),
77 followers: countOf(resolved.data, did, FOLLOWERS),
78 following: countOf(resolved.data, did, FOLLOWING),
79 vouches: countOf(resolved.data, did, VOUCHES)
80 };
81 const viewerFollowUri =
82 viewerDid && viewerDid !== did ? viewerUriOf(resolved.data, did, FOLLOWERS) : null;
83 const viewerFollowRkey = viewerFollowUri ? rkeyFromUri(viewerFollowUri) : viewerFollowUri;
84
85 const notJoined = !profile && Object.values(counts).every((n) => n === 0);
86
87 return {
88 identity: { did, handle: doc.handle },
89 profile,
90 counts,
91 viewerFollowRkey,
92 notJoined
93 };
94};